iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Software Development

全端工程師團隊的養成計畫系列 第 29 篇

Day29 從傳統post 到現在前後端分離,前端思維的改變

  • 分享至 

  • xImage
  •  

在實作了多項功能後,接下來想聊聊前後端分離所帶來的改變。
由於過去的經驗多半是以 MVC (Razor) 為主,因此這裡將以 MVC 模式 與本案的 前後端分離 (SPA) 模式 進行比較。

我們會以本案的實際需求作為範例,透過「查詢」與「編輯」這兩個需求,並以下方兩個 User Story 作為開發案例(圖29-1),來分析設計思維上的差異。那麼,當開發者接到這個任務時,會如何著手呢?

  • 身為一個管理者,我需要知道目前納管的授權軟體清單。
  • 身為一個管理者,我需要異動指定的授權軟體資清單資料,可以成功儲存。
    圖29-1:查詢軟體清單後,使用者可以進一步進行資料編輯。
    圖29-1

MVC 會怎麼設計

  1. 功能拆分為兩個獨立的 MVC 模組:
    • 查詢授權軟體清單
    • 編輯軟體資訊
  2. 功能間的關聯性:
    • 使用者先透過查詢功能找到目標軟體
    • 點選欲編輯的資料後,系統會跳轉至編輯頁面
    • 查詢頁跳轉至編輯頁時,需攜帶該筆軟體的 ID 以便載入正確的資料
  3. 查詢授權軟體清單功能
    • Controller 控制查詢條件,並透過 Model 撈取與整合資料
    • 開發查詢用的 ActionResult 回應
    • View 綁定 Model 後,負責繪製查詢結果畫面
    • 提供由 View 跳轉至編輯功能的導頁機制
    • JavaScript 基本防呆與驗證
  4. 編輯軟體資訊功能
    • 承接前一頁傳遞的軟體 ID 參數
    • 根據 ID,透過 Model 撈取資料庫中的軟體明細
    • View 綁定 Model,並繪製編輯用的表單畫面
    • 開發「儲存」的 ActionResult 回應
    • 開發「刪除」的 ActionResult 回應

前後端分離的設計

  1. 功能拆分:
    • 前端查詢授權軟體清單
    • 前端編輯軟體明細資訊
  2. 功能關聯:
    • 使用者於查詢畫面點選「編輯」後,可直接跳出視窗進行資料編輯
  3. 共用 Store 設計:
    • 建立並維護 store.ts
    • 定義 Dto 結構與共用的參數清單
    • 規劃後端對應的 Action
  4. 前端開發:
    • 開發查詢功能的 Vue 元件
    • 開發編輯功能的 Vue 元件
  5. 後端開發:
    • 實作 API,接收來自 store action 的請求並回應資料
  6. 前後端整合測試

兩者差異分析

在設計觀點上,前後端分離 特別重視「兩個功能間如何切換」,這會直接影響到共用 store 的設計方式。
透過直接存取共用的 store,可以實現 部分畫面即時更新,不僅提供更好的使用體驗,也提升了共用元件的彈性。

反觀 傳統 MVC,不同的 View 之間通常僅能依靠 TempData、Query String 或 Route Data 傳遞少量非敏感資料,來完成頁面間的串接。
雖然 MVC 提供了 Layout(整體骨架)與 Partial View(區塊重複利用)的機制,但最終仍是由伺服器產生一份完整的 HTML 回傳。
這種模式下,若僅有部分元素不同,往往需要再製作一份新的 Partial View,邏輯插入與差異化設計的空間有限,也難以進一步抽象成真正可重用的元件。

相較之下,Vue 的 動態渲染 機制可以靈活處理差異,並讓前端以更細緻的方式控制畫面更新,兩者之間仍有顯著差距。

MVC (Razor) vs 前後端分離 (SPA) – 功能設計差異比較

面向 MVC (Razor) 前後端分離 (SPA)
功能拆分 查詢、編輯各自獨立 Controller 與 View 查詢、編輯各自獨立 Vue 元件
功能關聯 查詢 → 導頁至編輯頁 (帶 ID) 查詢 → 點擊後於彈窗直接編輯
資料流 Controller 負責撈資料 + 傳遞至 View 前端透過 store.ts 管理共用 DTO,API 取資料
畫面開發 View 綁定 Model,使用 Razor + HTML 繪製 Vue 元件化開發,UI 與狀態解耦
互動模式 頁面跳轉式,功能邏輯與畫面耦合 單頁互動式,狀態驅動畫面更新
後端設計 ActionResult 處理查詢/編輯/刪除邏輯 提供 API action,單純資料存取與邏輯
前後端整合 前端依賴後端輸出完整 HTML 前端發送 API 請求,後端回傳 JSON
使用體驗 每次操作需換頁,流程較中斷 流暢無刷新體驗,互動性更好

運作原理差異

面向 MVC Partial View Vue 元件 (Component)
渲染流程 Controller 產生 Model → Razor 產生 HTML → 回傳整頁或片段 Component 內部 State/Props → Virtual DOM → 對比更新 → 更新 DOM
資料流 單向:Controller → View → HTML → Browser 雙向 / 單向:Props 從父元件傳入 → Component 內部 State 可雙向綁定 (v-model)
生命週期 只在伺服器端請求時生成 HTML 元件有完整生命周期 (created, mounted, updated, destroyed),可動態更新 DOM
互動更新 若要更新,通常需送表單或再發請求 (Post/Redirect) 可動態更新畫面,無需整頁刷新,事件驅動或響應式自動更新

實際看看動態更新的差異

以「休假申請單」為案例,表單開啟後通常會呈現申請的相關內容,不僅包含休假資訊,還需要顯示簽核歷程。
其中有一個區塊是 簽核流程。假設現在要新增一位簽核人員,如果希望在 點擊新增後只更新簽核清單區塊,而不是重新整個頁面(傳統 form post 的做法),那應該怎麼設計呢?
圖29-2:以請假單表單為範例,其中的簽核資訊在新增簽核人員後,需要能即時刷新更新。
圖29-2

MVC 做法

  1. 透過 ajax-post發送增加簽核的請求。
  2. 回傳的 Data HTML 片段)直接插入到網頁上 ID 為FlowSignList 的元素中完成動態更新內容,完成即時刷新需求。
$.ajax({
      type: "post", //使用 HTTP POST 方法發送請求
      url: getAPFunPath("Flow", "AddFlowSign"),
      data: JSON.stringify({
          "strFlowID": keys[0],
          "strSignID": keys[1],
          "strAddSignUser": addUserID,
      }),
      async: false,
      
      contentType: 'application/json; charset=utf-8',
      success: function (data) {
          if (data.indexOf("alert") > 0) {
              $("#error-div").html(data);
          } else {
              /*
                會將 AJAX 回傳的 data(通常是 HTML 片段)直接插入到網頁上 ID 為 AttachFileList 的元素中達到動態更新內容的標準做法,常用於檔案列表、表格、訊息等即時刷新
              */
              $("#FlowSignList").html(data); // 會將 AJAX 回傳的 data 插入到網頁上 ID 為 AttachFileList 的元素
              alert("新增成功");
          }
         
      },
      error: function (XMLHttpRequest, textStatus, errorThrown) {
          alert("error:AddFlowSign 有誤,請聯繫系統管理員,errorThrown : " + errorThrown);
      }
  });

雖然滿足了部分畫面的即時更新,但仍然有以下缺點:

  • 維護性與可讀性低 :jQuery 程式碼容易隨著功能增加而變得複雜,難以維護,目前範例僅為其中一個CRUD 的需求,通常系統中一個介面完成不同資料的 CRUD 並不少,全數透過 jQuery 達成畫面刷新,程式碼可讀性會大幅降低。
  • jQuery 只操作 DOM,無法自動追蹤資料狀態。每次資料變動都要手動更新畫面,容易漏掉或重複操作。
  • 元件化與重用性低 : 單純以 jQuery 選取器為主,難以拆分成可重用的元件。
  • 狀態管理困難:jQuery 只操作 DOM,無法自動追蹤資料狀態,每次資料變動都要手動更新畫面,容易漏掉或重複操作。

如果是 Vue 會怎麼做?

  1. 新增flowSignStore.ts
    • 定義新增時同步後端的 API addNewUser()
    • 取得回應後將資料回寫至 flowSignList
  2. 流程清單的畫面 vue 元件化
    • 元件畫面需要使用時,import flowSignStore.ts
    • 依需求再指定需要動態更新的事件中調用 flowSignStore.addNewUser
    • 顯示的元件內容雙向綁定 flowSignStore.flowSignList
 //flowSignStore.ts
  import { fetchWrapper , type ApiRspMessage ,type ActionRsp } from '@/utils/helpers/fetch-wrapper'; 
  const  FlowSignList as FlowSignListDto[];
  export const  useflowSignStore= defineStore({
  id: 'flowSignStore',
  state: () => ({
    flowSignList: []  as flowSignListDto[], 
  }),
  actions: {
    async addNewUser(userId: string) {
        try {
         const respData: ActionRsp [] = await fetchWrapper.post(apiURL,userId) as ActionRsp [];
         this.flowSignList = respData;

        }catch (error) {
           if (error instanceof Response) {
             const errorText = await error.text();
             console.error('Failed to create software:', errorText);
           } else {
             console.error('Failed to create software:', error);
           }
      }
    }


});

雖然一開始需要先開發元件或額外建立 Store,但後續就能重複使用,讓共用性最大化。不僅維護性更高、可讀性也大幅提升,更重要的是:透過自動追蹤資料狀態即可觸發畫面更新,無需額外撰寫事件或條件判斷來決定是否要刷新畫面,這點相當省事且值得投入。
以範例來說,當使用者點擊按鈕觸發事件並完成作業後,畫面能自動更新;若沒有這樣的設計,通常就只能依靠 setInterval 等定時機制來達成。

傳統作法 vs Vue 作法

面向 傳統作法 (MVC / jQuery) Vue 作法 (前後端分離 + Store)
畫面更新方式 透過整頁 Postback 或重新載入 HTML 透過 響應式資料綁定,自動更新部分畫面區塊
元件/邏輯共用性 低,常需重複撰寫或複製多份 Partial View 高,可將元件抽離並搭配 Store 重複使用
資料狀態追蹤 需要手動判斷與事件監控(e.g. callback, 判斷 flag) Vue 透過 響應式系統 自動追蹤狀態並刷新畫面
額外處理機制 常需用 setInterval 或手動觸發刷新 無需額外事件,資料變動即自動觸發畫面渲染
維護性 / 可讀性 較低,程式碼分散且重複 較高,結構清晰且能快速定位問題

Ending Remark

總結上述的分析,前後端分離後,開發與設計上的思維有著以下的不同:

  1. 功能切割上面需要考量更細緻,更注重再不同介面(功能)間的介面串接方式,包含共用 Store 與不同 vue 參數 props 傳遞、更細緻如props 傳遞物件結構也需要好好規劃。
  2. 開發會朝向高度共用與元件化:收到開發任務時不是僅考慮單一功能的完成,而去思考有哪些功能可再元件化、哪些可共用的方向開始著手,團隊中開發者,著手前先了解有哪些元件可以使用。
  3. Vue 前後端分離的架構下,前端(Vue)主要負責資料的呈現、使用者互動和介面邏輯,盡量避免處理複雜的業務邏輯、權限判斷、資料整理與重組。後端 API 專心負責負責資料存取、業務邏輯、權限驗證等核心功能。

上一篇
Day28 前後端分離的魔鬼與細節(二)
下一篇
Day30 從築夢到驗收 100%:我們的技術轉型挑戰成果
系列文
全端工程師團隊的養成計畫 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言